import { truthy } from '@gnlab/utils'
import { useRefValue } from '@gnlab/utils/env-react'
import { useObservable } from '@gnlab/utils/env-react/rx'
import { notification, Button } from 'antd'
import type { OrderedMap } from 'immutable'
import type { ListOnItemsRenderedProps, FixedSizeList as List } from 'react-window'
import { useStreams } from '@/lib/utils'
import services from '@/services'
import type {
  ExternalContact,
  InternalContact,
  NoticeContact,
  GroupContact,
  TransferedExternal,
} from '@/services/contact-types'
import { ContactType, compareContacts } from '@/services/contacts'
import type { Contact, ContactWithTransfer } from '@/services/contacts/'

// TODO: 是不是可以优化，只监听当前显示的联系人列表？

/**
 * 用于界面显示的联系人数据
 */
export interface ContactData<T extends Contact> {
  contact: T
  ownerCorp: string // 所属企微的主体
  ownerQywx: string // 所属企微名称
  unread: number // 未读消息数
  draft: string // 聊天草稿
  isCurrent: boolean // 是否是“当前联系人”
  isTop: boolean // 是否是“置顶联系人”
}

interface HookContacts {
  externals: ContactData<ExternalContact>[] | null
  transfers: ContactData<TransferedExternal>[] | null
  internals: ContactData<InternalContact | NoticeContact>[] | null
  groups: ContactData<GroupContact>[] | null
}
type ContactsData = NonNullable<HookContacts[keyof HookContacts]>

/**
 * 会话过滤模式
 */
export enum FilterMode {
  All = '全部聊天',
  Unread = '未读会话',
  Unanswered = '未回复会话',
}

/**
 * type: 当前要显示的联系人类型
 * filterMode: 是否只显示未读、未回复的联系人（当前联系人也会显示，不然任意联系人一点击就会从列表里消失了）
 */
export function useComputedContacts(
  type: Exclude<ContactType, ContactType.notice>,
  filterMode = FilterMode.All,
) {
  const srvQywxs = useMemo(() => services.qywxs, [])
  const srvContacts = useMemo(() => services.contacts, [])

  const { externals, transfers, groups, internals } = useContacts()
  const [unreads, drafts, tops, currentId, qywxs] = useStreams(
    services.chat.metas.unreads,
    services.chat.drafts.drafts,
    srvContacts.tops.items,
    services.contacts.current.id,
    srvQywxs.qywxs,
  )

  const filtered = useMemo(() => {
    if (filterMode === FilterMode.All) return { externals, transfers, groups, internals }
    function filter(contact: Contact) {
      if (contact.id === currentId) return true
      // 对于客户，Unread 模式虽然后端已经过滤一次了，但因为前端还随时能让客户变成已读，所以也需要把变成了已读的过滤掉
      return filterMode === FilterMode.Unread
        ? truthy(unreads.get(contact.id)?.unread)
        : contact.latestChat?.isSend === false
    }
    return {
      externals: externals.filter(filter),
      transfers: transfers.filter(filter),
      groups: groups.filter(filter),
      internals: internals.filter(filter),
    }
  }, [externals, transfers, groups, internals, currentId, unreads, filterMode])

  // 计算联系人数据
  const computeContact = useCallback(
    <T extends ContactWithTransfer>(contact: T): ContactData<T> => {
      let ownerCorp = ''
      let ownerQywx = ''
      if ('transfer' in contact) {
        ownerQywx = contact.transfer.ownerName
      } else {
        const qywx = qywxs.get(contact.ownerWxid)
        ownerCorp = qywx?.corpname ?? ''
        ownerQywx = qywx?.friendlyName ?? ''
      }

      const isTop = 'transfer' in contact ? false : tops.has(contact.id) // 涉及转交的联系人忽略“置顶”信息
      return {
        contact,
        ownerCorp,
        ownerQywx,
        isCurrent: currentId === contact.id,
        unread: unreads.get(contact.id)?.unread ?? 0,
        draft: drafts[contact.id] ?? '',
        isTop,
      }
    },
    [currentId, unreads, drafts, tops, qywxs],
  )

  const contactsData = useMemo((): HookContacts => {
    // 各类型联系人数据，在不需要显示时，均返回 null，省略计算过程
    const data: HookContacts = {
      // 对于客户，有搜索时显示搜索结果，否则显示客户列表（转交列表始终显示，但需要前端根据搜索关键词二次过滤）
      externals: type === ContactType.external ? filtered.externals.map(computeContact) : null,
      transfers:
        type === ContactType.external && filtered.transfers.length
          ? filtered.transfers.map(computeContact)
          : null,
      groups: type === ContactType.group ? filtered.groups.map(computeContact) : null,
      internals: type === ContactType.internal ? filtered.internals.map(computeContact) : null,
    }
    return data
  }, [type, filtered, computeContact])

  return {
    unreads: useUnreads(),
    contacts: contactsData,
  }
}

/**
 * 返回经过初步整理的联系人列表
 */
function useContacts() {
  const srvQywxs = useMemo(() => services.qywxs, [])
  const srvContacts = useMemo(() => services.contacts, [])

  const [allExternals, transfers, allGroups, allInternals, allNotices, tops, focused] = useStreams(
    services.externals.list,
    services.transfers.list,
    services.groups.list,
    services.internals.list,
    services.notices.list,
    srvContacts.tops.items,
    srvQywxs.focus.focused,
    services.chat.metas.unreads,
  )

  // 将指定类型联系人中的“置顶联系人”排到前面
  const topsFirst = useCallback(
    <T extends Contact>(source: OrderedMap<string, T>) => {
      const topContacts = tops
        .filter(info => source.has(info.id))
        .map(info => source.get(info.id)!)
        .sort(compareContacts)
        .values()
      const normalContacts = source.filter(contact => !tops.has(contact.id)).values()
      return [...topContacts, ...normalContacts]
    },
    [tops],
  )

  const externals = useMemo(
    () =>
      topsFirst(
        allExternals.filter(contact => {
          if (focused && contact.ownerWxid !== focused.wxid) return false // 有 focused 的情况下，只处理聚焦的企业微信的联系人
          if (transfers.has(contact.id)) return false // 涉及转交的客户单独返回
          return true
        }),
      ),
    [allExternals, focused, transfers, topsFirst],
  )

  const transfersList = useMemo(() => [...transfers.values()], [transfers])

  const groups = useMemo(
    () =>
      topsFirst(
        focused ? allGroups.filter(contact => contact.ownerWxid === focused.wxid) : allGroups,
      ),
    [allGroups, focused, topsFirst],
  )

  const internals = useMemo(() => {
    // 对于员工，也把 notices 包含进来
    const combined = allInternals.merge(allNotices)
    const filtered = focused
      ? combined.filter(contact => contact.ownerWxid === focused.wxid)
      : combined
    const sorted = filtered.sort(compareContacts)
    return topsFirst(sorted)
  }, [allInternals, allNotices, focused, topsFirst])

  return {
    externals,
    transfers: transfersList,
    groups,
    internals,
  }
}

/**
 * 处理各类联系人的未读消息总数
 */
function useUnreads() {
  const srvQywxs = useMemo(() => services.qywxs, [])

  const [metaUnreads, mutes, focusedQywx] = useStreams(
    services.chat.metas.unreads,
    services.groups.mutes,
    srvQywxs.focus.focused,
  )

  const reset = useCallback(
    (type: ContactType) => {
      const ownerWxid = focusedQywx ? focusedQywx.wxid : undefined
      services.chat.metas.resetUnreadOf(type, ownerWxid)
      // 对于员工类型，也要同步重置应用通知的未读数
      if (type === ContactType.internal)
        services.chat.metas.resetUnreadOf(ContactType.notice, ownerWxid)
    },
    [focusedQywx],
  )

  const unreads = useMemo(() => {
    const unreads = {
      [ContactType.external]: 0,
      [ContactType.group]: 0,
      [ContactType.internal]: 0,
    }
    for (const item of metaUnreads.values()) {
      if (focusedQywx && item.ownerWxid !== focusedQywx.wxid) continue
      if (item.type === ContactType.external) unreads[ContactType.external] += item.unread
      else if (item.type === ContactType.internal || item.type === ContactType.notice)
        unreads[ContactType.internal] += item.unread
      else if (item.type === ContactType.group) {
        // 对于群，只统计未“静音”的群的未读消息数
        if (!mutes.has(item.contactId)) unreads[ContactType.group] += item.unread
      }
    }
    return unreads
  }, [metaUnreads, mutes, focusedQywx])

  return {
    ...unreads,
    reset,
  }
}

/**
 * 显示客户转交通知
 */
export function useExternalTransferNotifications() {
  const srvTransfers = services.transfers

  useObservable(srvTransfers.received, contact => {
    const key = `notify-${Date.now()}`
    function confirm() {
      notification.destroy(key)
      services.contacts.current.mark(contact.id)
    }
    notification.info({
      key,
      message: `${contact.transfer.fromName} 向你转交客户「${contact.name}」`,
      description: contact.transfer.remark,
      duration: null,
      btn: <Button type="primary" size="small" onClick={confirm}>
        立即查看
      </Button>,
    })
  })

  useObservable(srvTransfers.backed, contact => {
    const key = `notify-${Date.now()}`
    function confirm() {
      notification.destroy(key)
    }
    notification.info({
      key,
      message: '转交客户收回提醒',
      description: `${contact.transfer.fromName} 收回了曾向你转交的客户 「${contact.transfer.transferName}」。`,
      duration: null,
      btn: <Button type="primary" size="small" onClick={confirm}>
        我知道了
      </Button>,
    })
  })

  useObservable(srvTransfers.returned, contact => {
    const key = `notify-${Date.now()}`
    function confirm() {
      notification.destroy(key)
      services.contacts.current.mark(contact.id)
    }
    notification.info({
      key,
      message: '转交客户退回提醒',
      description: `${contact.transfer.toName} 退回了你转交的客户 「${contact.transfer.transferName}」。`,
      duration: null,
      btn: <Button type="primary" size="small" onClick={confirm}>
        立即查看
      </Button>,
    })
  })
}

/**
 * 有联系人成为”当前联系人“时，保证其出现在视野里
 * 注意这里要基于 currentId 来触发，而不是整个 current 对象。
 * 因为聊天过程中联系人对象会重新生成，但“当前联系人”并没有变化，这里只希望在当前联系人变化时进行滚动，
 * 不然聊天行为会打乱滚动条位置。
 */
export function useShowCurrent(
  setType: (type: ContactType) => void,
  list: List<unknown> | null,
  contactsData: ContactsData,
) {
  const contactsDataRef = useRefValue(contactsData)
  const listRef = useRefValue(list)

  const { id: currentId, type: currentType } = useStreams(services.contacts.current.contact)[0] ?? {
    id: null,
    type: null,
  }
  useEffect(() => {
    if (currentId !== null) {
      setType(currentType)

      // 这里直接滚的话有概率导致 virtualized scroll 计算出错，显示出空白元素，要等一下再跳
      // （例如拉群后跳转）
      requestAnimationFrame(() => {
        const index = contactsDataRef.current.findIndex(item => item.contact.id === currentId)
        if (index > -1) listRef.current?.scrollToItem(index)
      })
    }
  }, [currentId, currentType, setType])
}

export interface ItemBaseProps {
  ownerCorp: string
  ownerQywx: string
  isCurrent: boolean
  isTop: boolean
  unread: number
  draft: string
  style?: React.CSSProperties
}
export type ContactItemProps = ItemBaseProps & {
  contact: ContactWithTransfer
}
export function useContactItem(ContactItem: (props: ContactItemProps) => JSX.Element) {
  // 此函数每次重新生成，会导致列表里的元素都重新“生成“（不是重新渲染，是重新生成组件并挂载），因此尽量不要有依赖
  const ContactItemRef = useRefValue(ContactItem)
  const Item = useCallback(
    ({
      data,
      index,
      style,
    }: {
      data: ContactsData
      index: number
      style: React.CSSProperties
    }) => <ContactItemRef.current style={style} {...data[index]!} />,
    [],
  )

  const itemKey = useCallback((index: number, list: ContactsData) => list[index]!.contact.id, [])

  return { Item, itemKey }
}

/**
 * 因为收到消息等原因导致联系人列表变化时，保证当前看到的联系人所处位置不变
 */
export function useKeepPositon(
  type: ContactType,
  contactsData: ContactsData,
  itemSize: number,
  list: List | null,
) {
  const typeRef = useRefValue(type)
  const contactsDataRef = useRefValue(contactsData)
  const listRef = useRefValue(list)

  const listOuterRef = useRef<HTMLElement>(null)
  const visibleFirstRef = useRef<{ type: ContactType; id: string; index: number } | null>(null)
  const onItemsRendered = useCallback((data: ListOnItemsRenderedProps) => {
    const index = data.visibleStartIndex
    const id = contactsDataRef.current[index]?.contact.id
    visibleFirstRef.current = id !== undefined ? { type: typeRef.current, id, index } : null
  }, [])

  useEffect(() => {
    const list = listRef.current
    const listOuter = listOuterRef.current
    const visibleFirst = visibleFirstRef.current
    if (!list || !listOuter || !visibleFirst || typeRef.current !== visibleFirst.type) return

    // 计算出前面插入了几个元素，来确定要修正多长的滚动条位置
    const currentIndex = contactsData.findIndex(ct => ct.contact.id === visibleFirst.id)
    const indexError = currentIndex !== -1 ? currentIndex - visibleFirst.index : 0
    if (indexError) {
      const fixedOffset = listOuter.scrollTop + indexError * itemSize
      list.scrollTo(fixedOffset)
    }
  }, [contactsData, itemSize])

  return { onItemsRendered, listOuterRef }
}
